#include <linux/inet.h>
#include <net/ipv6.h>
#include "socs/comm/types.h"
#include "socs/mod/addr_opt.h"

union ipv4_address {
	__be32 as32;
	__u8 as8[4];
};

int addr_6to4(struct in6_addr *src, struct ipv6_prefix *prefix, struct in_addr *dst)
{
	union ipv4_address dst_aux;

	switch (prefix->len) {
	case 32:
		dst_aux.as32 = src->s6_addr32[1];
		break;
	case 40:
		dst_aux.as8[0] = src->s6_addr[5];
		dst_aux.as8[1] = src->s6_addr[6];
		dst_aux.as8[2] = src->s6_addr[7];
		dst_aux.as8[3] = src->s6_addr[9];
		break;
	case 48:
		dst_aux.as8[0] = src->s6_addr[6];
		dst_aux.as8[1] = src->s6_addr[7];
		dst_aux.as8[2] = src->s6_addr[9];
		dst_aux.as8[3] = src->s6_addr[10];
		break;
	case 56:
		dst_aux.as8[0] = src->s6_addr[7];
		dst_aux.as8[1] = src->s6_addr[9];
		dst_aux.as8[2] = src->s6_addr[10];
		dst_aux.as8[3] = src->s6_addr[11];
		break;
	case 64:
		dst_aux.as8[0] = src->s6_addr[9];
		dst_aux.as8[1] = src->s6_addr[10];
		dst_aux.as8[2] = src->s6_addr[11];
		dst_aux.as8[3] = src->s6_addr[12];
		break;
	case 96:
		dst_aux.as32 = src->s6_addr32[3];
		break;
	default:
		/* Critical because enforcing valid prefixes is pool6's responsibility, not ours. */
		//WARN(true, "Prefix has an invalid length: %u.", prefix->len);
		return -EINVAL;
	}

	dst->s_addr = dst_aux.as32;
	return 0;
}

int str_to_addr4(const char *str, struct in_addr *result)
{
	return in4_pton(str, -1, (u8 *) result, '\0', NULL) ? 0 : -EINVAL;
}

int str_to_addr6(const char *str, struct in6_addr *result)
{
	return in6_pton(str, -1, (u8 *) result, '\0', NULL) ? 0 : -EINVAL;
}
/**
 *The kernel has a ipv6_addr_cmp(), but not a ipv4_addr_cmp().
 *Of course, that is because in_addrs are, to most intents and purposes, 32-bit integer values.
 *But the absence of ipv4_addr_cmp() does makes things look asymmetric.
 *So, booya.
 */
int ipv4_addr_cmp(const struct in_addr *a1, const struct in_addr *a2)
{
	return memcmp(a1, a2, sizeof(struct in_addr));
}
int addr_cmp(int family, const void *address1, const void *address2)
{
	if (family == IPV4) {
		return ipv4_addr_cmp((struct in_addr *)address1, (struct in_addr *)address2);
	} else {
		return ipv6_addr_cmp((struct in6_addr *)address1, (struct in6_addr *)address2);
	}
}

int addr_cmp_prefix(int family, const void *address1, const void *address2, int prefix)
{
	int ret = 0;

	if (family == IPV4) {
		return ipv4_addr_cmp((struct in_addr *)address1, (struct in_addr *)address2);
	} else {
		ret = memcmp(address1, address2, prefix / 8);

		if (ret == 0 && (prefix % 8 != 0)) {
			if ((((uint8_t *)address1)[prefix / 8] >> (8 - prefix % 8)) == (((
						uint8_t *)address2)[prefix / 8] >> (8 - prefix % 8))) {
				ret = 0;
			} else {
				ret = -1;
			}
		}

		return ret;
		//return memcmp(address1,address2,prefix);
		//return ipv6_addr_cmp((struct in6_addr *)address1,(struct in6_addr *)address2);
	}
}

int ifaddr_to_multaddr(const struct in6_addr *ifaddr, struct in6_addr *result)
{
	if (!ifaddr) {
		log_debug("ifaddr is null");
		return -1;
	}

	result->s6_addr32[0] = htonl(0xff020000);
	result->s6_addr32[1] = htonl(0x00000000);
	result->s6_addr32[2] = htonl(0x00000001);
	result->s6_addr32[3] = htonl(0xff000000);
	result->s6_addr[13] = ifaddr->s6_addr[13];
	result->s6_addr[14] = ifaddr->s6_addr[14];
	result->s6_addr[15] = ifaddr->s6_addr[15];
	return 0;
}
